<template>
  <DragResize
    v-if="showBox"
    :widget="{active: true, selectable: true}"
    :init-rect="boxRect"
    :is-draggable="boxDraggable"
    :is-resizable="boxResizable"
    :is-active="true" :minh="1" :minw="1"
    :snap-to-grid="true" :grid-x="1" :grid-y="1" :stick-size="5"
    :prevent-active-behavior="true"
    :class="{'display-none': invisible}"
    @resizing="onResize" @resizestop="stopResize"
    @dragging="onMove" @dragstop="stopMove">
    <div id="selection-box" :style="positionStyle" class="selection-box"></div>
  </DragResize>
</template>

<script>
  import DragResize from "@/components/IDE/DragResize.vue";
  import {mapActions, mapState} from "vuex";
  import {WidgetFactory} from "@/modules/IDE/widgetFactory";
  import {deepCopy} from "@/utils";
  import {widgetHashKeys} from "@/modules/IDE/widget/atom/widget";

  export default {
    name: 'WidgetSelectionBox',
    components: {
      DragResize
    },
    model: {
      event: "change",
      prop: "selectionBoxRect",
    },
    props: {
      showBox: {
        type: Boolean,
        default: false
      },
    },
    data() {
      return {
        invisible: false, // 按下 Shift+Alt 快捷时，隐藏 selectionBox
        boxRect: {left: 0, top: 0, width: 0, height: 0},
        lastBoxRect: {left: 0, top: 0, width: 0, height: 0}, // 记录上一次的位置，用于判断是否真正发生变更
        selectSingleWidgets: [], // 选中的组件单层级集合（过滤掉子级和父级都选中的子级组件）
        selectFlattenWidgets: [], // 选中的组件打平集合
        originalWidgetMap: {}, // 选中的原始组件集合，用于撤销/重做的旧数据记录
        originalRectMap: {}, // 记录所框选子组件的初始 rect 信息
      }
    },
    computed: {
      ...mapState({
        selectWidgetIds: state => state.ide.selectWidgetIds,
      }),
      positionStyle() {
        const {width, height} = this.boxRect;
        return {
          left: 0,
          top: 0,
          width: width + 'px',
          height: height + 'px',
        }
      },
      boxDraggable() {
        if (this.selectSingleWidgets.find(widget => widget.isRoot)) return false;
        return !!this.selectSingleWidgets.find(widget => !widget.isLocked && widget.draggable);
      },
      boxResizable() {
        if (this.selectSingleWidgets.find(widget => widget.isRoot)) return false;
        return !!this.selectSingleWidgets.find(widget => !widget.isLocked && widget.resizable);
      },
    },
    watch: {
      boxRect: {
        handler(rect) {
          this.updateSelectionBoxRect(rect);
        },
        immediate: true,
        deep: true
      },
	    showBox(show) {
        if (show) {
          this.initData();
        } else {
          this.updateWidgetsStickHiddenStatus(false);
        }
      },
      invisible(invisible) {
				if (!this.showBox) return;
        this.updateWidgetsStickHiddenStatus(!invisible);
      }
    },
    mounted() {
      document.addEventListener('keydown', this.keyboardHandle);
      document.addEventListener('keyup', this.keyboardUp);
    },
    beforeDestroy() {
      document.removeEventListener('keydown', this.keyboardHandle);
      document.removeEventListener('keyup', this.keyboardUp);
    },
    methods: {
      ...mapActions(['updateSelectionBoxRect']),
      keyboardHandle(event) {
	      const {tagName = ''} = event.target;
	      if (tagName === 'INPUT' || tagName === 'TEXTAREA') return;
        if (event.shiftKey) {
          this.invisible = true;
        }
      },
      keyboardUp(event) {
	      const {tagName = ''} = event.target;
	      if (tagName === 'INPUT' || tagName === 'TEXTAREA') return;
        if (event.key === 'Shift') {
          this.invisible = false;
        }
      },
      initData() {
        const selectSingleWidgets = [];
        // 查询某个子级的父级是否被选中，如果子级的父级也被选中，则将该子级去选
        const getIsSelectParent = (widgetId) => {
          const widget = WidgetFactory.getWidgetById(widgetId);
          let isSelectParent = false;
          if (widget.parentId) {
            if (this.selectWidgetIds.includes(widget.parentId)) {
              isSelectParent = true;
            } else {
              isSelectParent = getIsSelectParent(widget.parentId);
            }
          }
          return isSelectParent;
        }
        this.selectWidgetIds.forEach(widgetId => {
          const widget = WidgetFactory.getWidgetById(widgetId);
          if (!this.invisible) widget.hideStick = true;
          if (getIsSelectParent(widgetId)) return;
          selectSingleWidgets.push(widget);
        });
        this.selectSingleWidgets = selectSingleWidgets;
				this.getSelectFlattenWidgets();
				this.updateOriginalWidgetMap();
	      this.createSelectionBox();
      },
	    // 获取当前选中的所有组件的子组件集合
	    getSelectFlattenWidgets() {
		    let destWidgets = [];
		    this.selectWidgetIds.forEach(widgetId => {
			    const srcWidget = WidgetFactory.getWidgetById(widgetId);
			    const widgets = WidgetFactory.getWidgetFlattenChildren(srcWidget, false);
			    widgets.forEach(widget => {
				    destWidgets.push(widget);
				    const {relateSubPageId} = widget;
				    if (relateSubPageId) {
					    // 如果子页面的关联组件，则需要同时更改组件本身以及关联的子页面
					    const subPage = WidgetFactory.getWidgetById(relateSubPageId);
					    if (!subPage) return;
					    const subPageWidgets = WidgetFactory.getWidgetFlattenChildren(subPage, false);
					    subPageWidgets.forEach(widget => destWidgets.push(widget));
				    }
			    });
		    });
		    this.selectFlattenWidgets = destWidgets;
	    },
	    updateOriginalWidgetMap() {
		    this.selectFlattenWidgets.forEach(widget => {
			    this.originalWidgetMap[widget.id] = WidgetFactory.getWidgetNecessaryProperty(widget);
		    });
	    },
      // 创建选择框
      createSelectionBox() {
        // 获取所有选中组件的位置参数
        const rectArr = [];
        this.selectSingleWidgets.forEach(widget => {
          const rect = WidgetFactory.getWidgetAbsolutePosition(widget);
          rectArr.push({
            ...rect,
            right: rect.left + rect.width,
            bottom: rect.top + rect.height
          });
        });

        // 找出选择框的边界
        const minTop = Math.min.apply(null, rectArr.map(ele => ele.top));
        const minLeft = Math.min.apply(null, rectArr.map(ele => ele.left));
        const maxBottom = Math.max.apply(null, rectArr.map(ele => ele.bottom));
        const maxRight = Math.max.apply(null, rectArr.map(ele => ele.right));

        // 计算选择框的位置及宽高
        const selectionBoxRect = {
          left: minLeft,
          top: minTop,
          width: maxRight - minLeft,
          height: maxBottom - minTop
        };
        this.boxRect = selectionBoxRect;
				this.lastBoxRect = deepCopy(this.boxRect);
        this.$emit('change', selectionBoxRect);

        this.$nextTick(() => this.assembleRectData(), 100);
      },
      // 组装所选组件的初始 rect 数据
      assembleRectData() {
        // 记录初始的框选操作的子组件的 rect 信息
        this.originalRectMap = {};
        // 选择框边界值，多选时用于计算组件相对于选择框的偏移
        const minTop = Math.min.apply(null, this.selectSingleWidgets.map(ele => ele.rect.top));
        const minLeft = Math.min.apply(null, this.selectSingleWidgets.map(ele => ele.rect.left));

        this.selectFlattenWidgets.forEach(widget => {
          // 记录组件的初始宽高，及相对于选择框的 left、top值
          // 将组件的相对于父级的偏移值转换为相对于选择框的偏移值
          const {width, height, top, left} = widget.rect;

          const {states} = widget.UI;
          const fontSizes = states.map(state => ({
            textSize: state.textStyle && state.textStyle.size,
            iconSize: state.iconStyle && state.iconStyle.size
          }));

          // 组件宽高与选择框宽高相同时，不需要设置left、top值
          const noLeft = width === this.boxRect.width || left === minLeft;
          const noTop = height === this.boxRect.height || top === minTop;

          // 当前组件是否有选中父级，若有父级则relativeLeft、relativeTop为组件本身的left、top无需计算相对值
          // 多选时需计算组件相对于选择框的left、top值。计算方法：
          // 1.先找出所选组件最小的left值、及最小top值
          // 2. 用组件的left减去这个最小的left或top值得到相对值
          const hasParent = this.selectFlattenWidgets.some(ele => ele.id === widget.parentId);

					// 保存更改之前的组件状态
          this.originalRectMap[widget.id] = {
            ...widget.rect,
            relativeLeft: hasParent ? left : left - minLeft,
            relativeTop: hasParent ? top : top - minTop,
            noLeft, noTop, hasParent, fontSizes
          };
        });

        // 选择框原始宽高
        this.originalBoxWidth = this.boxRect.width;
        this.originalBoxHeight = this.boxRect.height;
      },
      onMove(rect = {}) {
	      const {left = 0, top = 0, width = 0, height = 0} = rect;
        const rectDiff = {left: left - this.boxRect.left, top: top - this.boxRect.top};
        this.selectionBoxMove(rectDiff);
	      this.boxRect = {left, top, width, height};
      },
      // 选择框移动，更新所有选中组件位置
      selectionBoxMove(boxRect) {
        const {left, top} = boxRect;
        this.selectSingleWidgets.forEach(widget => {
          widget.rect.left += left;
          widget.rect.top += top;
        });
      },
      stopMove(rect = {}) {
				if (WidgetFactory.judgeWidgetRectEqual(this.lastBoxRect, rect)) return;
	      this.lastBoxRect = deepCopy(this.boxRect);
        this.$emit('change', rect);
	      // 记录组件变更前后的值，用于撤销/重做相关处理
	      const widgetChangeArr = [];
	      this.selectSingleWidgets.forEach(widget => {
		      // 记录每个被更改组件的变更前后的值
		      const oldWidget = this.originalWidgetMap[widget.id];
		      const newWidget = WidgetFactory.getWidgetNecessaryProperty(widget);
		      widgetChangeArr.push({from: oldWidget, to: newWidget});
	      });
	      WidgetFactory.undoRedoHdl.operationHdl([{operationType: 'edit', widgets: widgetChangeArr}]);
				this.updateOriginalWidgetMap();
      },
      onResize(rect = {}, currentStick) {
	      const {left = 0, top = 0, width = 0, height = 0} = rect;
        const rectDiff = {
          width: width,
          height: height,
          widthDiff: width - this.boxRect.width,
          heightDiff: height - this.boxRect.height,
        };
        this.selectionBoxResize(rectDiff, currentStick);
	      this.boxRect = {left, top, width, height};
      },
      // 基于选择框的拖拽缩放
      selectionBoxResize(rectDiff, stick) {
        const {width: boxWidth, height: boxHeight, widthDiff, heightDiff} = rectDiff;
        // 计算选择框横向和纵向上的缩放比例（当前的宽度或高度 - 原始宽度或高度）/ 原始宽度或高度 + 1
        const wRatio = (boxWidth - this.originalBoxWidth) / this.originalBoxWidth + 1;
        const hRatio = (boxHeight - this.originalBoxHeight) / this.originalBoxHeight + 1;
	      const sizeRatio = Math.min(wRatio, hRatio);
        const minTop = Math.min.apply(null, this.selectSingleWidgets.map(ele => ele.rect.top));
        const minLeft = Math.min.apply(null, this.selectSingleWidgets.map(ele => ele.rect.left));
        this.selectFlattenWidgets.forEach(widget => {
          const {left, top} = widget.rect;
          // 计算组件新的宽度和高度（组件原始的宽高 * 缩放比例）
          let newWidth = Math.round(this.originalRectMap[widget.id].width * wRatio);
          let newHeight = Math.round(this.originalRectMap[widget.id].height * hRatio);

          // 计算组件的位置偏移
          const {noLeft, noTop, hasParent, relativeLeft, relativeTop} = this.originalRectMap[widget.id];
          let newLeft = left;
          let newTop = top;

          // 相对于选择框的X轴位置偏移计算（相对于选框的left值 * 缩放比例 + 选框边界的left值)
          if (!noLeft && !hasParent) {
            newLeft = Math.round(relativeLeft * wRatio + minLeft);
          } else if (hasParent) {  // 相对于父级的偏移
            newLeft = Math.round(relativeLeft * wRatio);
          }
          // 相对于选择框的Y轴位置偏移计算（相对于选框的top值 * 缩放比例 + 选框边界的top值)
          if (!noTop && !hasParent) {
            newTop = Math.round(relativeTop * hRatio + minTop);
          } else if (hasParent) {  // 相对于父级的偏移
            newTop = Math.round(relativeTop * hRatio);
          }

          // 向上拖拽时，top值减小、height值增加
          if (stick[0] === 't') {
            newTop -= heightDiff;
            newHeight -= heightDiff;
          }
          // 向左拖拽时，left值减小、width值增加
          if (stick[1] === 'l') {
            newLeft = newLeft - widthDiff;
            newWidth = newWidth - widthDiff;
          }

          widget.rect = {width: newWidth, height: newHeight, left: newLeft, top: newTop};
					// 缩放字体及图标
	        const {fontSizes = []} = this.originalRectMap[widget.id];
	        widget.UI.states.forEach((state, idx) => {
		        const {textSize, iconSize} = fontSizes[idx];
		        if (state.textStyle) {
			        state.textStyle.size = Math.round(textSize * sizeRatio);
		        }
		        if (state.iconStyle) {
			        state.iconStyle.size = Math.round(iconSize * sizeRatio);
		        }
	        });
					// 特殊处理 数值进步器
	        if (widget.category === 'inputNumber') {
		        const {iconStyle = {}} = widget.UI.data || {};
		        const iconSize = iconStyle.size;
		        iconSize && (iconStyle.size  = Math.round(iconSize * sizeRatio));
	        }
        });
      },
      stopResize(rect = {}) {
	      if (WidgetFactory.judgeWidgetRectEqual(this.lastBoxRect, rect)) return;
	      this.lastBoxRect = deepCopy(this.boxRect);
        this.$emit('change', rect);
	      // 记录组件变更前后的值，用于撤销/重做相关处理
	      const widgetChangeArr = [];
	      this.selectFlattenWidgets.forEach(widget => {
		      // 记录每个被更改组件的变更前后的值
		      const oldWidget = this.originalWidgetMap[widget.id];
		      const newWidget = WidgetFactory.getWidgetNecessaryProperty(widget);
		      widgetChangeArr.push({from: oldWidget, to: newWidget});
	      });
	      WidgetFactory.undoRedoHdl.operationHdl([{operationType: 'edit', widgets: widgetChangeArr}]);
	      this.updateOriginalWidgetMap();
      },
      // 更新组件 stick hidden 状态
      updateWidgetsStickHiddenStatus(hide = false) {
        this.selectWidgetIds.forEach(widgetId => {
          const widget = WidgetFactory.getWidgetById(widgetId);
          widget.hideStick = hide;
        });
      },
    }
  }
</script>

<style lang="less" scoped>
  .selection-box {
    position: absolute;
    background: rgba(112, 192, 255, 0.06);
    border: 1px solid #70c0ff;
  }
</style>
